package com.yanyu.space.sys.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yanyu.space.common.auth.support.AuthUtil;
import com.yanyu.space.common.auth.bean.enums.AuthType;
import com.yanyu.space.common.core.bean.auth.SpaceUser;
import com.yanyu.space.sys.bean.enums.EnabledEnum;
import com.yanyu.space.sys.bean.enums.MenuTypeEnum;
import com.yanyu.space.sys.bean.enums.SysRedisKeyEnum;
import com.yanyu.space.sys.bean.po.Menu;
import com.yanyu.space.sys.bean.po.RoleMenu;
import com.yanyu.space.sys.bean.po.User;
import com.yanyu.space.sys.bean.vo.common.TreeSelect;
import com.yanyu.space.sys.bean.vo.login.MetaVo;
import com.yanyu.space.sys.bean.vo.login.RouterVo;
import com.yanyu.space.sys.bean.vo.menu.RoleMenuTreeselectVo;
import com.yanyu.space.sys.dao.MenuMapper;
import com.yanyu.space.sys.dao.RoleMenuMapper;
import com.yanyu.space.sys.service.IMenuService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 系统菜单 业务实现类
 *
 * @author yanyu
 */
@Service
@Slf4j
@AllArgsConstructor
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IMenuService {

    private RoleMenuMapper roleMenuMapper;
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public List<RouterVo> getRouters() {
        SpaceUser spaceUser = AuthUtil.getSession(AuthType.ADMIN_TYPE);
        List<Menu> menus;
        if (spaceUser.isAdmin()) {
            menus = baseMapper.listMenuAll();
        } else {
            menus = baseMapper.listMenuByUserId(spaceUser.getId());
        }
        menus = menus.parallelStream().filter(menuTmp -> MenuTypeEnum.M.match(menuTmp.getType()) || MenuTypeEnum.C.match(menuTmp.getType())).collect(Collectors.toList());

        List<Menu> finalMenus = menus;
        return finalMenus.parallelStream()
                .filter(childMenu -> childMenu.getParentId().equals(0L))
                .map(menuTmp -> {
                    RouterVo routerVoTmp = new RouterVo();
                    routerVoTmp.setName(StringUtils.capitalize(menuTmp.getPath()));
                    routerVoTmp.setHidden(EnabledEnum.NO.match(menuTmp.getEnabled()));
                    routerVoTmp.setChildren(this.buildRoutes(menuTmp, finalMenus));
                    meta(menuTmp, routerVoTmp);
                    alwaysShow(menuTmp, routerVoTmp);
                    component(menuTmp, routerVoTmp);
                    routerVoTmp.setHidden(menuTmp.getVisible().equals(0) ? Boolean.TRUE : Boolean.FALSE);
                    routerVoTmp.setComponent("Layout");
                    return routerVoTmp;
                })
                .collect(Collectors.toList());
    }

    @Override
    public List<TreeSelect> treeselect() {
        List<Menu> menus =
                baseMapper.selectList(Wrappers.<Menu>lambdaQuery().orderByAsc(Menu::getSort));

        return menus.parallelStream()
                .filter(menuTmp -> menuTmp.getParentId().equals(0L))
                .map(menuTmp -> {
                    TreeSelect treeSelect = new TreeSelect();
                    treeSelect.setId(menuTmp.getId());
                    treeSelect.setLabel(menuTmp.getName());
                    treeSelect.setChildren(buildTreeselect(menuTmp, menus));
                    return treeSelect;
                }).collect(Collectors.toList());
    }

    @Override
    public RoleMenuTreeselectVo roleMenuTreeselect(Long roleId) {
        RoleMenuTreeselectVo vo = new RoleMenuTreeselectVo();
        List<TreeSelect> menus = treeselect();
        vo.setMenus(menus);

        List<Long> roleMenuCheckKeys = baseMapper.listCheckMenuByRoleId(roleId);
        vo.setCheckedKeys(roleMenuCheckKeys);
        return vo;
    }

    @Override
    public List<Menu> list(Menu menu) {
        List<Menu> menus = baseMapper.selectList(Wrappers.<Menu>lambdaQuery()
                .like(StringUtils.isNoneBlank(menu.getName()), Menu::getName, menu.getName())
                .eq(menu.getType() != null, Menu::getType, menu.getType())
                .orderByAsc(Menu::getSort));
        return menus;
    }

    @Override
    public Set<String> listAllocated(Long userId) {
        String redisKey = SysRedisKeyEnum.MENU_OF_USER.getCode(userId);
        boolean redisKeyFlag = stringRedisTemplate.hasKey(redisKey);
        if (redisKeyFlag) {
            Set<String> menuPerms = stringRedisTemplate.opsForSet().members(redisKey);
            log.info("从缓存中查询用户关联菜单信息, 用户id：[{}], 关联菜单信息：[{}]",
                    userId, menuPerms.parallelStream().collect(Collectors.joining(",")));
            return menuPerms;
        }

        User user = new User();
        user.setId(userId);
        List<Menu> menus = baseMapper.listMenuByUserId(userId);
        Set<String> menuPerms = menus.parallelStream().filter(menu -> MenuTypeEnum.A.match(menu.getType())
                || MenuTypeEnum.Q.match(menu.getType())).map(Menu::getPerms).collect(Collectors.toSet());
        if (CollectionUtils.isNotEmpty(menuPerms)) {
            log.info("从数据库中获取用户关联菜单信息, 用户id：[{}], 关联菜单信息：[{}]",
                    userId, menuPerms.parallelStream().collect(Collectors.joining(",")));
            stringRedisTemplate.opsForSet().add(SysRedisKeyEnum.MENU_OF_USER.getCode(userId),
                    menuPerms.toArray(new String[]{}));
            stringRedisTemplate.expire(SysRedisKeyEnum.MENU_OF_USER.getCode(userId),
                    SysRedisKeyEnum.MENU_OF_USER.getTimeOut(0), SysRedisKeyEnum.MENU_OF_USER.getUnit());
            return menuPerms;
        } else {
            log.info("当前用户没有关联菜单信息, 用户id：[{}]", userId);
            return new HashSet<>();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id) {
        baseMapper.deleteById(id);
        roleMenuMapper.delete(Wrappers.<RoleMenu>lambdaQuery().eq(RoleMenu::getMenuId, id));
    }

    private List<TreeSelect> buildTreeselect(Menu menu, List<Menu> menus) {
        return menus.parallelStream()
                .filter(menuTmp -> menuTmp.getParentId().equals(menu.getId()))
                .map(menuTmp -> {
                    TreeSelect treeSelect = new TreeSelect();
                    treeSelect.setId(menuTmp.getId());
                    treeSelect.setLabel(menuTmp.getName());
                    treeSelect.setChildren(buildTreeselect(menuTmp, menus));
                    return treeSelect;
                }).collect(Collectors.toList());
    }

    /**
     * 构建路由信息
     *
     * @param menu
     * @param menus
     * @return
     */
    private List<RouterVo> buildRoutes(Menu menu, List<Menu> menus) {
        return menus.parallelStream()
                .filter(childMenu -> childMenu.getParentId().equals(menu.getId()))
                .map(menuTmp -> {
                    RouterVo routerVoTmp = new RouterVo();
                    routerVoTmp.setName(StringUtils.capitalize(menuTmp.getPath()));
                    routerVoTmp.setHidden(EnabledEnum.NO.match(menuTmp.getEnabled()));
                    routerVoTmp.setChildren(this.buildRoutes(menuTmp, menus));
                    meta(menuTmp, routerVoTmp);
                    alwaysShow(menuTmp, routerVoTmp);
                    component(menuTmp, routerVoTmp);
                    routerVoTmp.setHidden(menuTmp.getVisible().equals(0) ? Boolean.TRUE : Boolean.FALSE);
                    return routerVoTmp;
                })
                .collect(Collectors.toList());
    }

    /**
     * @param routerVO
     */
    private void alwaysShow(Menu menu, RouterVo routerVO) {
        if (CollectionUtils.isNotEmpty(routerVO.getChildren()) && MenuTypeEnum.M.match(menu.getType())) {
            routerVO.setAlwaysShow(Boolean.TRUE);
            routerVO.setRedirect("noRedirect");
        }
    }

    /**
     * 组件
     */
    private void component(Menu menu, RouterVo routerVO) {
        String component = "Layout";
        if (notTopM(menu)) {
            component = "ParentView";
        } else if (menu.getFrameFlag().equals(0) && StringUtils.startsWithAny(menu.getPath(), "http", "https")) {
            component = "InnerLink";
        } else if (MenuTypeEnum.C.match(menu.getType())) {
            component = menu.getComponent();
        }
        routerVO.setComponent(component);
    }

    /**
     * @param menu
     * @param routerVO
     */
    private void meta(Menu menu, RouterVo routerVO) {
        if (menu.getFrameFlag().equals(1) || (menu.getFrameFlag().equals(0) && StringUtils.startsWithAny(menu.getPath(), "http", "https"))) {
            routerVO.setPath(menu.getPath());
            routerVO.setMeta(new MetaVo(menu.getName(), menu.getIcon(), menu.getCacheFlag().equals(0), menu.getPath()));
        } else {
            if (menu.getParentId().intValue() == 0) {
                routerVO.setPath(StringUtils.join("/" + menu.getPath()));
            } else {
                routerVO.setPath(menu.getPath());
            }
            routerVO.setMeta(new MetaVo(menu.getName(), menu.getIcon(), menu.getCacheFlag().equals(0), null));
        }
    }

    /**
     * 非顶级目录
     *
     * @param menu
     * @return
     */
    private boolean notTopM(Menu menu) {
        return menu.getParentId().intValue() != 0 && MenuTypeEnum.M.match(menu.getType());
    }
}
